package core;

import annotation.Controller;
import annotation.RequestMapping;
import config.PackageConfig;
import enums.RequestType;
import exception.ServerException;
import exception.UnknownException;
import request.Request;
import response.Response;
import util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 转发请求给特定的Controller中的特定处理方法，返回处理结果
 */
public class RequestMappingAdapter {

    private RequestMappingAdapter() { // 全为静态成员的类，禁止实例化
        throw new AssertionError();
    }

    private static final Map<RequestType, Method> forwardingTable;

    static {
        List<Class<?>> controllerList = ClassUtils.getClasses(PackageConfig.CONTROLLER_PACKAGE_NAME);

        // mapping RequestType to Method
        forwardingTable = new HashMap<>();
        controllerList.stream().filter(clazz -> clazz.isAnnotationPresent(Controller.class)) // 1. filter @Controller annotation's class
            .forEach(clazz -> Arrays.stream(clazz.getDeclaredMethods()).filter(method -> method.isAnnotationPresent(RequestMapping.class)) // 2. filter @RequestMapping annotation's method
                .forEach(method -> Arrays.stream(method.getAnnotation(RequestMapping.class).value()) // 3. mapping particular RequestType to particular Method
                    .forEach(requestType -> forwardingTable.put(requestType, method))));
    }

    public static Response handle(Request request) {
        try {
            if (!forwardingTable.containsKey(request.getType())) {
                throw new NoSuchMethodException();
            }
            // 1. get RequestType handle method
            Method requestMappingMethod = forwardingTable.get(request.getType());
            // 2. get singleton controller from Singleton Factory
            Object controller = SingletonFactory.getSingleton(requestMappingMethod.getDeclaringClass());
            // 3. Calling handle method using reflection, return Response made by handle method
            return (Response) requestMappingMethod.invoke(controller, request);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof ServerException) {
                ServerException serverException = (ServerException) e.getTargetException();
                InetAddress address = Handler.addressThreadLocal.get();
                int port = Handler.portThreadLocal.get();
                System.err.println(LocalDateTime.now() + "\t" + address.getHostAddress() + ":" + port + "\t" + serverException.getErrorCode().getMessage());
                throw serverException;
            }
            e.printStackTrace();
            throw new UnknownException();
        } catch (Exception e) {
            e.printStackTrace();
            throw new UnknownException();
        }
    }
}
